/*
 * Copyright (c) Forge Development LLC and contributors
 * SPDX-License-Identifier: LGPL-2.1-only
 */
package net.minecraftforge.gradle;

import org.gradle.api.Named;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;

import java.util.Map;

/// The configuration options for Slime Launcher tasks.
///
/// The launch tasks generated by the [Minecraft][MinecraftExtension] extension are specialized
/// [JavaExec][org.gradle.api.tasks.JavaExec] tasks that are designed to work with Slime Launcher, Minecraft Forge's
/// dedicated launcher for the development environment. While the implementing task class remains internal (can still be
/// configured as type `JavaExec`), these options exist to allow consumers to alter or add pre-defined attributes to the
/// run configurations as needed.
///
/// For example, changing the [main class][org.gradle.api.tasks.JavaExec#getMainClass()] of the launch task as a
/// `JavaExec` task will cause it to not use Slime Launcher and skip its configurations for it. If a consumer wishes to
/// use Slime Launcher but change the main class it delegates to after initial setup, that can be done using
/// [Property#set] on [#getMainClass()].
public interface SlimeLauncherOptions extends Named {
    @Override
    @Input String getName();

    /// The main class for Slime Launcher to use.
    ///
    /// This is the class that will be invoked by Slime Launcher, **not** the main class of the
    /// [org.gradle.api.tasks.JavaExec] task that will be produced from these options.
    ///
    /// @return A property for the main class
    @Input @Optional Property<String> getMainClass();

    /// The arguments to pass to the main class.
    ///
    /// This is the arguments that will be passed to the main class through Slime Launcher, **not** the arguments for
    /// Slime Launcher itself.
    ///
    /// @return A property for the arguments to pass to the main class
    @Input @Optional ListProperty<String> getArgs();

    /// The JVM arguments to use.
    ///
    /// These are applied immediately when Slime Launcher is executed. A reminder that Slime Launcher is not a
    /// re-launcher but a dev-environment bootstrapper.
    ///
    /// @return A property for the JVM arguments
    @Input @Optional ListProperty<String> getJvmArgs();

    /// The classpath to use.
    ///
    /// The classpath in question **must include** Slime Launcher in it. By default, the [minecraft][MinecraftExtension]
    /// extension adds Slime Launcher as a dependency to the consuming [project's][org.gradle.api.Project]
    /// [runtimeClasspath][org.gradle.api.plugins.JavaPlugin#RUNTIME_CLASSPATH_CONFIGURATION_NAME] configuration.
    ///
    /// Keep in mind that if the [org.gradle.api.tasks.JavaExec] task is configured to have a different [main
    /// class][org.gradle.api.tasks.JavaExec#getMainClass()], the classpath does not need to include Slime Launcher.
    ///
    /// @return The classpath to use
    @InputFiles @Classpath @Optional ConfigurableFileCollection getClasspath();

    /// The minimum memory heap size to use.
    ///
    /// Working with this property is preferred over manually using the `-Xms` argument in the [JVM
    /// arguments][#getJvmArgs()].
    ///
    /// @return A property for the minimum heap size
    @Input @Optional Property<String> getMinHeapSize();

    /// The maximum memory heap size to use.
    ///
    /// Working with this property is preferred over manually using the `-Xmx` argument in the [JVM
    /// arguments][#getJvmArgs()].
    ///
    /// @return A property for the maximum heap size
    @Input @Optional Property<String> getMaxHeapSize();

    /// The system properties to use.
    ///
    /// @return A property for the system properties
    @Input @Optional MapProperty<String, String> getSystemProperties();

    /// The environment variables to use.
    ///
    /// @return A property for the environment variables
    @Input @Optional MapProperty<String, String> getEnvironment();

    /// The working directory to use.
    ///
    /// By default, this will be `run/`{@link #getName() name}.
    ///
    /// To clarify: this is the working directory of the Java process. Slime Launcher uses absolute file locations to
    /// place its caches and metadata, which do not interfere with the working directory.
    ///
    /// @return A property for the working directory
    @Internal DirectoryProperty getWorkingDir();

    /// Adds to the arguments to pass to the [main class][#getMainClass()].
    ///
    /// @param args The arguments to add
    /// @apiNote To add multiple arguments, use [#args(Object...)]
    /// @see #getArgs()
    void args(Object args);

    /// Adds to the arguments to pass to the [main class][#getMainClass()].
    ///
    /// @param args The arguments to add
    /// @apiNote Unlike [#setArgs(String...)], this method does not replace the existing arguments.
    /// @see #getArgs()
    void args(Object... args);

    /// Adds to the arguments to pass to the [main class][#getMainClass()].
    ///
    /// @param args The arguments to add
    /// @apiNote Unlike [ListProperty#set(Iterable)], this method does not replace the existing arguments.
    /// @see #getArgs()
    void args(Iterable<?> args);

    /// Adds to the arguments to pass to the [main class][#getMainClass()].
    ///
    /// @param args The arguments to add
    /// @apiNote Unlike [ListProperty#set(Provider)], this method does not replace the existing arguments.
    /// @see #getArgs()
    void args(Provider<? extends Iterable<?>> args);

    /// Sets the arguments to pass to the [main class][#getMainClass()].
    ///
    /// @param args The arguments
    /// @see #getArgs()
    void setArgs(String... args);

    /// Adds to the JVM arguments to use.
    ///
    /// @param jvmArgs The JVM argument to add
    /// @apiNote To add multiple arguments, use [#jvmArgs(Object...)]
    /// @see #getJvmArgs()
    void jvmArgs(Object jvmArgs);

    /// Adds to the JVM arguments to use.
    ///
    /// @param jvmArgs The JVM arguments to add
    /// @apiNote Unlike [#setJvmArgs(Object...)], this method does not replace the existing arguments.
    /// @see #getJvmArgs()
    void jvmArgs(Object... jvmArgs);

    /// Adds to the JVM arguments to use.
    ///
    /// @param jvmArgs The JVM arguments to add
    /// @apiNote Unlike [ListProperty#set(Iterable)], this method does not replace the existing arguments.
    /// @see #getJvmArgs()
    void jvmArgs(Iterable<?> jvmArgs);

    /// Adds to the JVM arguments to use.
    ///
    /// @param jvmArgs The JVM arguments to add
    /// @apiNote Unlike [ListProperty#set(Provider)], this method does not replace the existing arguments.
    /// @see #getJvmArgs()
    void jvmArgs(Provider<? extends Iterable<?>> jvmArgs);

    /// Sets the JVM arguments to use.
    ///
    /// @param jvmArgs The arguments
    /// @see ListProperty#set(Iterable)
    void setJvmArgs(Object... jvmArgs);

    /// Adds to the classpath to use.
    ///
    /// @param classpath The classpath to include with the existing classpath
    /// @apiNote Unlike [#setClasspath(Object...)], this method does not replace the existing classpath.
    /// @see #getClasspath()
    default void classpath(Object... classpath) {
        this.getClasspath().from(classpath);
    }

    /// Adds to the classpath to use.
    ///
    /// @param classpath The classpath to include with the existing classpath
    /// @apiNote Unlike [#setClasspath(Iterable)], this method does not replace the existing classpath.
    /// @see #getClasspath()
    default void classpath(Iterable<?> classpath) {
        this.getClasspath().from(classpath);
    }

    /// Adds to the classpath to use.
    ///
    /// @param classpath The classpath to include with the existing classpath
    /// @apiNote Unlike [#setClasspath(FileCollection)], this method does not replace the existing classpath.
    /// @see #getClasspath()
    default void classpath(FileCollection classpath) {
        this.getClasspath().from(classpath);
    }

    /// Sets the classpath to use.
    ///
    /// @param classpath The classpath
    /// @apiNote This method will replace the existing classpath. To add to it, use [#classpath(Object...)].
    /// @see #getJvmArgs()
    default void setClasspath(Object... classpath) {
        this.getClasspath().setFrom(classpath);
    }

    /// Sets the classpath to use.
    ///
    /// @param classpath The classpath
    /// @apiNote This method will replace the existing classpath. To add to it, use [#classpath(Iterable)].
    /// @see #getJvmArgs()
    default void setClasspath(Iterable<?> classpath) {
        this.getClasspath().setFrom(classpath);
    }

    /// Sets the classpath to use.
    ///
    /// @param classpath The classpath
    /// @apiNote This method will replace the existing classpath. To add to it, use [#classpath(FileCollection)].
    /// @see #getJvmArgs()
    default void setClasspath(FileCollection classpath) {
        this.getClasspath().setFrom(classpath);
    }

    /// Adds a single system property to use.
    ///
    /// @param name  The name
    /// @param value The value
    /// @apiNote To add multiple system properties at once, use [#systemProperties(Provider)].
    /// @see #getSystemProperties()
    void systemProperty(String name, Object value);

    /// Adds to the system properties to use.
    ///
    /// @param properties The system properties
    /// @apiNote Unlike [MapProperty#set(Map)], this method does not replace the existing system properties. To add a
    /// single property, use [#systemProperty(String,Object)].
    /// @see #getSystemProperties()
    void systemProperties(Map<String, ?> properties);

    /// Adds to the system properties to use.
    ///
    /// @param properties The system properties
    /// @apiNote Unlike [MapProperty#set(Provider)], this method does not replace the existing system properties. To add
    /// a single property, use [#systemProperty(String,Object)].
    /// @see #getSystemProperties()
    void systemProperties(Provider<? extends Map<String, ?>> properties);

    /// Adds a single environment variable to use.
    ///
    /// @param name  The name
    /// @param value The value
    /// @apiNote To add multiple environment variables at once, use [#environment(Provider)].
    /// @see #getEnvironment()
    void environment(String name, Object value);

    /// Adds to the environment variables to use.
    ///
    /// @param properties The environment variables
    /// @apiNote Unlike [MapProperty#set(Map)], this method does not replace the existing environment variables. To add
    /// a single variable, use [#environment(String,Object)].
    /// @see #getEnvironment()
    void environment(Map<String, ?> properties);

    /// Adds to the environment variables to use.
    ///
    /// @param properties The environment variables
    /// @apiNote Unlike [MapProperty#set(Provider)], this method does not replace the existing environment variables. To
    /// add a single variable, use [#environment(String,Object)].
    /// @see #getEnvironment()
    void environment(Provider<? extends Map<String, ?>> properties);
}
